﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.ComponentModel;
namespace TransportLayer.NodeLibrary
{
    public class Caller : Cleaner
    {
        private string name;
        public string Name { get { return name; } }
        public string VnNCName { get; private set; }
        private Socket socket;
        public bool Connected { get; private set; }
        byte[] buffer = new byte[4096];
        //protocol session id
        private Dictionary<int, CallerConnection> Connections;
        public List<CallerConnection> ReadyConnections { get; private set; }
        public string Message { get; private set; }
        public int Port { get { return ((IPEndPoint)socket.LocalEndPoint).Port; } }
        private string Address { get { return ((IPEndPoint)socket.LocalEndPoint).Address.ToString(); } }
        private int id;


        public Caller(int id, string name)
        {
            Connections = new Dictionary<int, CallerConnection>();
            ReadyConnections = new List<CallerConnection>();
            this.name = name;
            this.id = id;
            Listen();
        }

        private void Listen()
        {   
            if(socket != null)
                socket.Close();
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint andPoint = new IPEndPoint(IPAddress.Any, 0); //any means at all network interfaces
            this.socket.Bind(andPoint);
            this.socket.Listen(10);
            this.socket.BeginAccept(OnClientConnect, this.socket);
        }

         private void OnClientConnect(IAsyncResult asyn)
         {            
            socket = this.socket.EndAccept(asyn);
            //int received = socket.Receive(buffer);
            //string[] login = (Encoding.ASCII.GetString(buffer, 0, received)).Split(' ');
            socket.Send(Encoding.ASCII.GetBytes("REGISTER " + id + " " + name));
            Message = "REGISTER... ";
            int received = socket.Receive(buffer);
            string[] login = (Encoding.ASCII.GetString(buffer, 0, received)).Split(' ');
            if (login.Length == 2 && login[0] == "OK")
            {
                VnNCName = login[1];
                Connected = true;
                socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, OnDataReceived, socket);
                Message = "REGISTER OK";
            }
            else
            {
                Connected = false;
                Listen();
                this.socket.BeginAccept(OnClientConnect, this.socket);
                Message = "REGISTER FAIL";
            }

        }

        public void BeginCall(string targetName, int size)
        {
            int r;
            CallerConnection conn = new CallerConnection(this.name, this.id, targetName, -1, size);
            socket.Send(Encoding.ASCII.GetBytes(conn.ToInvite()));
            Connections.Add(conn.ConnectionRand, conn);
            this.Message = "Invite " + name + "...";
        }

        public void EndCall(int id)
        {
            CallerConnection conn;
            if (Connections.TryGetValue(id,out conn))
            {
                socket.Send(Encoding.ASCII.GetBytes(conn.ToBye()));
                Connections.Remove(id);
                this.Message = "Call " + id + " bye.";
            }
        }

        private void OnDataReceived(IAsyncResult asyn)
        {
            int r;
            try { r = socket.EndReceive(asyn); }
            catch (SocketException) { socket.Close(); this.Message = "Call has been lost."; return; }
            string recv = Encoding.ASCII.GetString(buffer, 0, r);
            Process(recv);
            socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, OnDataReceived, socket);
        }

        private void Process(string recv)
        {
            string[] query = recv.Split(' ');
            if(query.Length == 1)
                 socket.Send(Encoding.ASCII.GetBytes(Headers.CANCEL.ToString()));
            else if (query.Length >= 2)
            {
                int ConnectionRand;
                try
                {
                    ConnectionRand = Int32.Parse(query[1]);
                }
                catch (Exception) { return;  }
                CallerConnection conn ;
                if (!Connections.TryGetValue(ConnectionRand, out conn))
                {
                    conn = new CallerConnection();
                }
                switch (query[0])
                { 
                    case "INVITE":
                        conn.ParseInvite(query);
                        Connections.Add(conn.ConnectionRand, conn);
                         Message ="INVITE " + conn.ToString();
                        socket.Send(Encoding.ASCII.GetBytes(conn.ToTrying()));
                        Message ="TRYING " + conn.ToString();
                        Thread.Sleep(400);
                        socket.Send(Encoding.ASCII.GetBytes(conn.ToRinging()));
                        Message ="RINGING " + conn.ToString();
                        Thread.Sleep(400);
                        socket.Send(Encoding.ASCII.GetBytes(conn.ToOK()));
                        Message = "OK " + conn.ToString();
                        if(conn.Connected)
                            ReadyConnections.Add(conn);
                        break;
                    case "TRYING":
                        conn.ParseTrying(query);
                        Message = "TRYING " + conn.ToString();
                        break;
                    case "OK":
                        Message = "OK " + conn.ToString();
                        conn.ParseOK(query);
                        if(conn.Connected)
                        ReadyConnections.Add(conn);
                        break;
                    case "RINGING":
                        Message = "RINGING " + conn.ToString();
                        conn.ParseRinging(query);
                        break;
                    case "CANCEL":
                        Message = "CANCEL " + conn.ToString();
                        ReadyConnections.Remove(conn);
                        Connections.Remove(conn.ConnectionRand);
                        break;
                    case "BYE":
                        Message = "BYE " + conn.ToString();
                        ReadyConnections.Remove(conn);
                        Connections.Remove(conn.ConnectionRand);
                        break;
                    default:
                        break;
                }

            }
        }

        public void ByeConnection(CallerConnection connection)
        {
            Message = "BYE " + connection.ToString();
            socket.Send(Encoding.ASCII.GetBytes(connection.ToBye()));
            ReadyConnections.Remove(connection);
        }


        public void Clean()
        {
            if(socket!=null)
            socket.Close();
            Connected = false;
        }
    }
}
